Bidirectional Sync¶

This demo mirrors https://niivue.com/demos/features/sync.bidirectional.html.

In [1]:
from pathlib import Path

from ipyniivue import download_dataset

BASE_API_URL = "https://niivue.com/demos/images/"
DATA_FOLDER = Path("images")

# Download data for example
download_dataset(
    BASE_API_URL,
    DATA_FOLDER,
    files=[
        "pcasl.nii.gz",
        "aal.nii.gz",
        "mni152.nii.gz",
    ],
)
pcasl.nii.gz already exists.
aal.nii.gz already exists.
mni152.nii.gz already exists.
Dataset downloaded successfully to images.
In [2]:
import ipywidgets as widgets
from IPython.display import display

from ipyniivue import MultiplanarType, NiiVue

## Create Three NiiVue Instances

nv1 = NiiVue(
    height=400,
    multiplanar_force_render=True,
    back_color=(0.0, 0.0, 0.0, 1.0),
)

nv2 = NiiVue(
    height=400,
    multiplanar_force_render=True,
    back_color=(0.0, 0.0, 0.0, 1.0),
)

nv3 = NiiVue(
    height=400,
    multiplanar_force_render=True,
    back_color=(0.0, 0.0, 0.0, 1.0),
)

## Load Volumes

nv1.load_volumes([{"path": DATA_FOLDER / "pcasl.nii.gz"}])
nv2.load_volumes([{"path": DATA_FOLDER / "aal.nii.gz"}])
nv3.load_volumes([{"path": DATA_FOLDER / "mni152.nii.gz"}])

## Create Interactive Controls

layout_dropdown = widgets.Dropdown(
    options=[
        ("Auto", MultiplanarType.AUTO),
        ("Column", MultiplanarType.COLUMN),
        ("Grid", MultiplanarType.GRID),
        ("Row", MultiplanarType.ROW),
    ],
    value=MultiplanarType.AUTO,
    description="Layout:",
)

sync_dropdown = widgets.Dropdown(
    options=[
        ("Sync Disabled", 0),
        ("Sync 2D", 1),
        ("Sync 3D", 2),
        ("Sync 2D and 3D", 3),
    ],
    value=3,
    description="Broadcast:",
)

status1 = widgets.HTML(value=" ")
status2 = widgets.HTML(value=" ")
status3 = widgets.HTML(value=" ")

## Setup Event Handlers


def on_layout_change(change):
    """Handle layout dropdown changes."""
    new_layout = change["new"]
    nv1.opts.multiplanar_layout = new_layout
    nv2.opts.multiplanar_layout = new_layout
    nv3.opts.multiplanar_layout = new_layout


def on_sync_change(change):
    """Handle sync mode dropdown changes."""
    v = change["new"]
    is_2d = False
    is_3d = False

    if v % 2:  # If odd (1 or 3)
        is_2d = True
    if v > 1:  # If 2 or 3
        is_3d = True

    nv1.broadcast_to([nv2, nv3], {"2d": is_2d, "3d": is_3d})
    nv2.broadcast_to([nv1, nv3], {"2d": is_2d, "3d": is_3d})
    nv3.broadcast_to([nv1, nv2], {"2d": is_2d, "3d": is_3d})


@nv1.on_location_change
def handle_location1(data):
    """Handle location string."""
    status1.value = f"  {data['string']}"


@nv2.on_location_change
def handle_location2(data):
    """Handle location string."""
    status2.value = f"  {data['string']}"


@nv3.on_location_change
def handle_location3(data):
    """Handle location string."""
    status3.value = f"  {data['string']}"


layout_dropdown.observe(on_layout_change, names="value")
sync_dropdown.observe(on_sync_change, names="value")

on_sync_change({"new": sync_dropdown.value})

## Display All

controls = widgets.HBox([layout_dropdown, sync_dropdown])

viewers = widgets.GridspecLayout(1, 3, height="400px")
viewers[0, 0] = nv1
viewers[0, 1] = nv2
viewers[0, 2] = nv3

status_row = widgets.HBox([status1, status2, status3])

display(widgets.VBox([controls, viewers, status_row]))